package bold.storage.core;

import bold.storage.ex.NotBackException;
import bold.storage.splice.StrKit;
import bold.storage.util.FilterUtil;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.*;
import java.sql.Date;
import java.util.*;

public class DB {
    private static DruidDataSource dataSource;
    private static final Logger log = LoggerFactory.getLogger(DB.class);
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public DB(DruidDataSource dataSource) {
        this(dataSource, true);
    }

    public DB(DruidDataSource dataSource, boolean filter) {
        DB.dataSource = dataSource;
        if (filter) filterInit(dataSource.getUrl());
    }

    private void filterInit(String url) {
        String newUrl = url.substring(0, url.indexOf("?"));
        String base = newUrl.substring(newUrl.lastIndexOf("/") + 1);
        FilterUtil.init(base);
    }

    private static DruidDataSource getDataSource() {
        return dataSource;
    }

    private static Connection prepareConnection() throws SQLException {
        if (getDataSource() == null) {
            throw new SQLException("获取连接失败，请检查数据库配置参数及数据库服务器是否正常");
        } else {
            return getDataSource().getConnection();
        }
    }

    public void close() {
        if (dataSource != null) {
            dataSource.close();
        }

    }

    private static PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }

    protected static PreparedStatement prepareStatement(Connection conn, String sql, int returnedKeys) throws SQLException {
        return conn.prepareStatement(sql, returnedKeys);
    }

    private static void fillStatement(PreparedStatement ps, Object... params) throws SQLException {
        ParameterMetaData pmd = ps.getParameterMetaData();
        int stmtCount = pmd.getParameterCount();
        int paramsCount = params == null ? 0 : params.length;
        if (stmtCount != paramsCount) {
            throw new SQLException("Wrong number of parameters: expected " + stmtCount + ", was given " + paramsCount);
        } else if (params != null) {
            for (int i = 0; i < params.length; ++i) {
                if (params[i] != null) {
                    ps.setObject(i + 1, params[i]);
                } else {
                    ps.setNull(i + 1, 12);
                }
            }

        }
    }

    private static SQLException rethrow(SQLException cause, String sql, Object... params) {
        cause.printStackTrace();
        String causeMessage = cause.getMessage();
        if (causeMessage == null) {
            causeMessage = "";
        }

        StringBuffer msg = new StringBuffer(causeMessage);
        msg.append(" Query: ");
        msg.append(sql);
        msg.append(" Parameters: ");
        if (params == null) {
            msg.append("[]");
        } else {
            msg.append(Arrays.deepToString(params));
        }

        SQLException e = new SQLException(msg.toString(), cause.getSQLState(), cause.getErrorCode());
        e.setNextException(cause);
        return e;
    }

    private static void close(Connection conn) throws SQLException {
        if (conn != null) {
            conn.close();
        }

    }

    private static void close(Statement stmt) throws SQLException {
        if (stmt != null) {
            stmt.close();
        }

    }

    private static void close(ResultSet rs) throws SQLException {
        if (rs != null) {
            rs.close();
        }

    }

    public static int[] batch(String sql, Object[][] params) throws SQLException {
        Connection conn = threadLocal.get();
        if (conn == null) {
            conn = prepareConnection();
        }

        return batch(conn, sql, params);
    }

    private static int[] batch(Connection conn, String sql, Object[][] params) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (sql == null) {
            close(conn);
            throw new SQLException("Null SQL statement");
        } else if (params == null) {
            close(conn);
            throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
        } else {
            PreparedStatement stmt = null;
            int[] rows = null;

            try {
                stmt = prepareStatement(conn, sql);

                for (int i = 0; i < params.length; ++i) {
                    fillStatement(stmt, params[i]);
                    stmt.addBatch();
                }

                rows = stmt.executeBatch();
            } catch (SQLException var9) {
                log.error(rethrow(var9, sql, (Object[]) params).getMessage());
            } finally {
                close(stmt);
                if (conn.getAutoCommit()) {
                    close(conn);
                }

            }

            return rows;
        }
    }

    public static String clob2String(Clob clob) throws SQLException {
        StringBuilder sb = new StringBuilder(1024);
        char[] buffer = new char[1024];
        Reader reader = clob.getCharacterStream();

        try {
            int count;
            try {
                while ((count = reader.read(buffer)) != -1) {
                    sb.append(buffer, 0, count);
                }
            } catch (IOException var13) {
                throw new SQLException(var13);
            }
        } finally {
            try {
                reader.close();
            } catch (IOException var12) {
            }

            clob.free();
        }

        return sb.toString();
    }

    /**
     * 不需要 转换数据 不需要
     */
    private static Object convert(Object obj) throws SQLException {
        if (null == obj) {
            return null;
        }
        if (obj instanceof Clob) {
            obj = clob2String((Clob) obj);
        } else if (obj instanceof BigDecimal) {
            obj = ((BigDecimal) obj).toPlainString();
        } else if (obj instanceof Timestamp) {
            obj = ((Timestamp) obj).getTime();
        } else if (obj instanceof Date) {
            obj = ((Date) obj).getTime();
        }
        return obj;
    }


    public static RecordList query(String sql) throws SQLException {
        return query(prepareConnection(), sql, (Object[]) null);
    }

    public static RecordList query(String sql, Object... params) throws SQLException {
        return query(prepareConnection(), sql, params);
    }

    public static Record queryOne(String sql, Object... params) throws SQLException {
        RecordList records = query(prepareConnection(), "SELECT * FROM (" + sql + ") AS a limit 1", params);
        return records.size() > 0 ? records.get(0) : null;
    }

    private static RecordList query(Connection conn, String sql, Object... params) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (sql == null) {
            close(conn);
            throw new SQLException("Null SQL statement");
        } else {
            PreparedStatement stmt = null;
            ResultSet rs = null;
            RecordList result = new RecordList();
            try {
                stmt = prepareStatement(conn, sql);
                fillStatement(stmt, params);
                rs = stmt.executeQuery();
                ResultSetMetaData metaData = rs.getMetaData();

                int columnCount = metaData.getColumnCount();

                while (rs.next()) {
                    Record cell = new Record();

                    for (int i = 0; i < columnCount; ++i) {
                        cell.put(metaData.getColumnLabel(i + 1), convert(rs.getObject(i + 1)));
                    }

                    result.add(cell);
                }

                RecordList var38 = result;
                return var38;
            } catch (SQLException var35) {
                log.error(rethrow(var35, sql, params).getMessage());
            } finally {
                try {
                    close(rs);
                } finally {
                    close(stmt);
                    close(conn);
                }
            }

            return result;
        }
    }

    public static <T> T query(String sql, Class<T> clazz, Object... params) throws SQLException {
        T entity;
        if (clazz.isInterface()) {
            log.error("Specified class is an interface");
            throw new SQLException("Specified class is an interface :{" + clazz.getName() + "}");
        }
        try {
            entity = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error("泛型失败创建");
            throw new SQLException("泛型失败创建 :{" + clazz.getName() + "}");
        }
        return query(prepareConnection(), entity, "SELECT * FROM (" + sql + ") AS a limit 1", params);
    }

    private static <T> T query(Connection conn, T entity, String sql, Object... params) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (entity == null) {
            throw new SQLException("对象创建失败");
        } else if (sql == null) {
            close(conn);
            throw new SQLException("Null SQL statement");
        } else {
            PreparedStatement stmt = null;
            ResultSet rs = null;

            try {
                stmt = prepareStatement(conn, sql);
                fillStatement(stmt, params);
                rs = stmt.executeQuery();
                ResultSetMetaData metaData = rs.getMetaData();
                int columnCount = metaData.getColumnCount();

                while (rs.next()) {
                    for (int i = 0; i < columnCount; ++i) {
                        try {
                            BeanUtils.setProperty(entity, StrKit.underlineToCamel(metaData.getColumnLabel(i + 1)), rs.getObject(i + 1));
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            log.error("设置 entity 失败",e);
                            throw new IllegalStateException(e);
                        }
                    }
                }
            } catch (SQLException var35) {
                log.error(rethrow(var35, sql, params).getMessage());
            } finally {
                try {
                    close(rs);
                } finally {
                    close(stmt);
                    close(conn);
                }
            }
            return entity;
        }
    }

    public static List<Object> queryList(String sql) throws SQLException {
        return queryList(prepareConnection(), sql, (Object[]) null);
    }

    public static List<Object> queryList(String sql, Object... params) throws SQLException {
        return queryList(prepareConnection(), sql, params);
    }

    private static List<Object> queryList(Connection conn, String sql, Object... params) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (sql == null) {
            close(conn);
            throw new SQLException("Null SQL statement");
        } else {
            PreparedStatement stmt = null;
            ResultSet rs = null;
            List<Object> result = new ArrayList<>();
            try {
                stmt = prepareStatement(conn, sql);
                fillStatement(stmt, params);
                rs = stmt.executeQuery();
                while (rs.next()) {
                    result.add(convert(rs.getObject(1)));
                }
                return result;
            } catch (SQLException var35) {
                log.error(rethrow(var35, sql, params).getMessage());
            } finally {
                try {
                    close(rs);
                } finally {
                    close(stmt);
                    close(conn);
                }
            }
            return result;
        }
    }

    public static Object queryObject(String sql, Object... params) throws SQLException {
        return queryObject(prepareConnection(), sql, params);
    }

    public static String queryString(String sql, String defaultValue, Object... params) throws SQLException {
        Object obj = queryObject(sql, params);
        return Record.getString(obj, defaultValue);
    }

    public static Integer queryInteger(String sql, Integer defaultValue, Object... params) throws SQLException {
        Object obj = queryObject(sql, params);
        return Record.getInteger(obj, defaultValue);
    }

    public static Long queryLong(String sql, Long defaultValue, Object... params) throws SQLException {
        Object obj = queryObject(sql, params);
        return Record.getLong(obj, defaultValue);
    }

    public static Double queryDouble(String sql, Double defaultValue, Object... params) throws SQLException {
        Object obj = queryObject(sql, params);
        return Record.getDouble(obj, defaultValue);
    }

    private static Object queryObject(Connection conn, String sql, Object... params) throws SQLException {
        PreparedStatement stmt = null;
        ResultSet rs = null;

        Object var5;
        try {
            stmt = prepareStatement(conn, sql);
            fillStatement(stmt, params);
            rs = stmt.executeQuery();
            if (!rs.next()) {
                var5 = null;
                return var5;
            }

            var5 = convert(rs.getObject(1));
        } finally {
            close(rs);
            close(stmt);
            close(conn);
        }

        return var5;
    }

    public static Page paginate(String sql, int currPage, int pageSize, Object... params) throws SQLException {
        if (sql == null || !sql.contains("limit")) {
            return new Page(null, currPage, pageSize, 0, 0);
        }

        StringBuffer totalSb = new StringBuffer();
        totalSb.append("select count(1) from(").append(sql.substring(0, sql.indexOf("limit"))).append(") ttt");
        int totalRow = queryInteger(totalSb.toString(), 0, params);
        totalSb.setLength(0);

        if (totalRow == 0) {
            return new Page(null, currPage, pageSize, 0, 0);
        }

        RecordList records = query(sql, params);

        int totalPage = totalRow / pageSize;
        if (totalRow % pageSize != 0) {
            ++totalPage;
        }

        return new Page(records, currPage, pageSize, totalPage, totalRow);

    }

    public static Page page(String sql, int currPage, int pageSize, Object... params) throws SQLException {
        StringBuffer totalRowSql = new StringBuffer();
        totalRowSql.append("select count(1) from(").append(sql).append(") ttt");
        int totalRow = queryInteger(totalRowSql.toString(), 0, params);
        totalRowSql.setLength(0);
        currPage = currPage <= 0 ? 1 : currPage;
        pageSize = pageSize <= 1 ? 15 : pageSize;
        if (totalRow == 0) {
            return new Page(null, currPage, pageSize, 0, 0);
        } else {
            StringBuffer newSql = new StringBuffer();
            newSql.append(sql).append(" limit ").append((currPage - 1) * pageSize).append(",").append(pageSize);
            RecordList records = query(newSql.toString(), params);
            newSql.setLength(0);
            int totalPage = totalRow / pageSize;
            if (totalRow % pageSize != 0) {
                ++totalPage;
            }

            return new Page(records, currPage, pageSize, totalPage, totalRow);
        }
    }

    public static int update(Connection conn, String sql) throws SQLException {
        return update(conn, sql, (Object[]) null);
    }

    public static int update(String sql) throws SQLException {
        Connection conn = threadLocal.get();
        if (conn == null) {
            conn = prepareConnection();
        }

        return update(conn, sql, (Object[]) null);
    }

    public static int update(String sql, Object... params) throws SQLException {
        Connection conn = threadLocal.get();
        if (conn == null) {
            conn = prepareConnection();
        }

        return update(conn, sql, params);
    }

    private static int update(Connection conn, String sql, Object... params) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (sql == null) {
            close(conn);
            throw new SQLException("Null SQL statement");
        } else {
            PreparedStatement stmt = null;
            int rows;
            try {
                stmt = prepareStatement(conn, sql);
                fillStatement(stmt, params);
                rows = stmt.executeUpdate();
            } catch (SQLException var9) {
                log.error(rethrow(var9, sql, params).getMessage());
                conn.rollback();
                throw new SQLException(var9);
            } finally {
                close(stmt);
                if (conn.getAutoCommit()) {
                    close(conn);
                }

            }

            return rows;
        }
    }

    public static int insert(Connection conn, String sql) throws SQLException {
        return insert(conn, sql, (Object[]) null);
    }

    public static int insert(String sql) throws SQLException {
        Connection conn = threadLocal.get();
        if (conn == null) {
            conn = prepareConnection();
        }

        return insert(conn, sql, (Object[]) null);
    }

    public static int insert(String sql, Object... params) throws SQLException {
        Connection conn = threadLocal.get();
        if (conn == null) {
            conn = prepareConnection();
        }

        return insert(conn, sql, params);
    }

    private static int insert(Connection conn, String sql, Object... params) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (sql == null) {
            close(conn);
            throw new SQLException("Null SQL statement");
        } else {
            PreparedStatement stmt = null;

            int var4;
            try {
                stmt = conn.prepareStatement(sql);
                fillStatement(stmt, params);
                var4 = stmt.executeUpdate();
            } catch (SQLException var8) {
                log.error(rethrow(var8, sql, params).getMessage());
                if (!conn.getAutoCommit()) {
                    conn.rollback();
                }

                throw new SQLException(var8);
            } finally {
                close(stmt);
                if (conn.getAutoCommit()) {
                    close(conn);
                }

            }

            return var4;
        }
    }

    public static int[] insertBatch(String sql, List<Map<String, Object>> paramList) throws SQLException {
        Connection conn = threadLocal.get();
        if (conn == null) {
            conn = prepareConnection();
        }

        return insertBatch(conn, sql, paramList);
    }

    private static int[] insertBatch(Connection conn, String sql, List<Map<String, Object>> paramList) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (sql == null) {
            close(conn);
            throw new SQLException("Null SQL statement");
        } else if (paramList == null) {
            close(conn);
            throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
        } else {
            PreparedStatement stmt = null;
            try {
                stmt = prepareStatement(conn, sql);
                Iterator var5 = paramList.iterator();

                while (var5.hasNext()) {
                    Map<String, Object> map = (Map) var5.next();
                    fillStatement(stmt, map.values().toArray());
                    stmt.addBatch();
                }

                int[] result = stmt.executeBatch();
                return result;
            } catch (SQLException var10) {
                log.error(var10.getMessage());
                if (!conn.getAutoCommit()) {
                    conn.rollback();
                }

                throw new SQLException(var10);
            } finally {
                close(stmt);
                if (conn.getAutoCommit()) {
                    close(conn);
                }

            }
        }
    }

    /**
     * var6  决定try 里面哪种异常回滚
     */
    public static void tx(Runnable runnable) throws SQLException {
        startTransaction();
        try {
            runnable.run();
            getConnectionLocal().commit();
        } catch (Exception var6) {
            if (!(var6 instanceof NotBackException)) {
                getConnectionLocal().rollback();
            }
        } finally {
            closeTransaction();
        }

    }

    static Connection getConnectionLocal() {
        return threadLocal.get();
    }

    static void startTransaction() {
        try {
            Connection conn = threadLocal.get();
            if (conn == null) {
                conn = prepareConnection();
                conn.setAutoCommit(false);
                threadLocal.set(conn);
            }

        } catch (SQLException var1) {
            throw new RuntimeException(var1);
        }
    }

    static void closeTransaction() {
        try {
            Connection conn = threadLocal.get();
            if (conn != null) {
                conn.setAutoCommit(true);
                close(conn);
                threadLocal.remove();
            }

        } catch (SQLException var1) {
            throw new RuntimeException(var1);
        }
    }
}
